﻿using System.Collections;
using System.Diagnostics;

namespace DimensionsHelper.Client.Controls;

internal class BorderGridCellMatrix
{
    private readonly WeakReference<BorderGrid> _grid;
    private readonly BorderGridCell?[,] _storage;


    internal BorderGridCellMatrix(WeakReference<BorderGrid> grid, int sizeX, int sizeY)
    {
        ArgumentNullException.ThrowIfNull(grid);
        Debug.Assert(sizeX >= 0 && sizeY >= 0);

        _grid = grid;
        SizeX = sizeX;
        SizeY = sizeY;
        _storage = new BorderGridCell?[sizeX, sizeY];
    }


    public int SizeX { get; }

    public int SizeY { get; }

    public int Count { get; private set; }


    public BorderGridCell this[int indexX, int indexY]
    {
        get
        {
            ValidateIndex(indexX, indexY);

            BorderGridCell? cell = _storage[indexX, indexY];

            if (cell == null)
            {
                cell = new BorderGridCell(_grid, indexX, indexY);
                Count++;
                _storage[indexX, indexY] = cell;
                SetNeighbourhood(cell);
            }

            return cell;
        }
    }


    public BorderGridCell this[int index]
    {
        get
        {
            if (index < 0 || index >= SizeX * SizeY)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

            int indexX = index / SizeY;
            int indexY = index % SizeY;

            BorderGridCell? cell = _storage[indexX, indexY];

            if (cell == null)
            {
                cell = new BorderGridCell(_grid, indexX, indexY);
                Count++;
                _storage[indexX, indexY] = cell;
                SetNeighbourhood(cell);
            }

            return cell;
        }
    }


    private void ValidateIndex(int indexX, int indexY)
    {
        if (indexX < 0 || indexX >= SizeX)
        {
            throw new ArgumentOutOfRangeException(nameof(indexX));
        }

        if (indexY < 0 || indexY >= SizeY)
        {
            throw new ArgumentOutOfRangeException(nameof(indexY));
        }
    }


    private void SetNeighbourhood(BorderGridCell cell)
    {
        int r = cell.Row;
        int c = cell.Column;

        if (r > 0)
        {
            BorderGridCell? top = _storage[r - 1, c];

            if (top != null)
            {
                cell.Top = top;
                top.Bottom = cell;
            }
        }

        if (r < SizeY - 1)
        {
            BorderGridCell? bottom = _storage[r + 1, c];

            if (bottom != null)
            {
                cell.Bottom = bottom;
                bottom.Top = cell;
            }
        }

        if (c > 0)
        {
            BorderGridCell? left = _storage[r, c - 1];

            if (left != null)
            {
                cell.Left = left;
                left.Right = cell;
            }
        }

        if (c < SizeY - 1)
        {
            BorderGridCell? right = _storage[r, c + 1];

            if (right != null)
            {
                cell.Right = right;
                right.Left = cell;
            }
        }
    }


    public Enumerable Slice(int x1, int y1, int x2, int y2)
    {
        return new Enumerable(this, x1, y1, x2, y2);
    }


    public readonly struct Enumerable : IEnumerable<BorderGridCell>
    {
        private readonly Enumerator _enumerator;

        internal Enumerable(BorderGridCellMatrix matrix, int x1, int y1, int x2, int y2)
        {
            _enumerator = new Enumerator(matrix, x1, y1, x2, y2);
        }

        public readonly IEnumerator GetEnumerator()
        {
            return _enumerator;
        }

        readonly IEnumerator<BorderGridCell> IEnumerable<BorderGridCell>.GetEnumerator()
        {
            return _enumerator;
        }
    }

    private struct Enumerator : IEnumerator<BorderGridCell>
    {
        private readonly BorderGridCellMatrix _matrix;
        private BorderGridCell? _current;
        private int _currentX;
        private int _currentY;
        private readonly int _startX;
        private readonly int _startY;
        private readonly int _endX;
        private readonly int _endY;

        internal Enumerator(BorderGridCellMatrix matrix, int startX, int startY, int endX, int endY)
        {
            _matrix = matrix;
            _startX = startX;
            _startY = startY;
            _endX = endX;
            _endY = endY;

            _currentX = _startX - 1;
            _currentY = _startY;
        }


        public readonly object Current => _current!;

        readonly BorderGridCell IEnumerator<BorderGridCell>.Current => _current!;


        public readonly void Dispose()
        {
        }


        public bool MoveNext()
        {
            _currentX++;

            if (_currentX > _endX)
            {
                _currentX = _startX;
                _currentY++;
            }

            if (_currentY > _endY)
            {
                return false;
            }

            _current = _matrix[_currentX, _currentY];
            return true;
        }


        public void Reset()
        {
            _current = null;
            _currentX = _startX - 1;
            _currentY = _startY;
        }
    }
}